﻿/**
 *@description: 视频屏-闪现Demo
 *@file: flash.cpp
 *@author: snow-tyan
 *@email: zziywang@163.com
 *@date: 8 February 2023
 *@copyright: 2018-2022 AiRiA. All rights reserved.
 */

#include "mygl/util.h"
#include "mygl/shader.h"
#include "mygl/texture2d.h"
#include "mygl/resource_manager.h"
#include "mygl/video_renderer.h"
#include "mygl/video_postprocessing.h"

#include "lenovo_object.h"
#include "screen_template.h"

#include <iostream>
#include <fstream>
#include <opencv2/opencv.hpp>
#include <GLFW/glfw3.h>

#ifndef PROJECT_ROOT
#define PROJECT_ROOT "F:/VisualStudioProjects/my-open-gl"
#endif

using namespace mygl;
// Project Variable
const std::string kShaderRoot = PROJECT_ROOT + std::string("/LenovoTest/shaders/");
const std::string kTextureRoot = PROJECT_ROOT + std::string("/resources/textures/");
const std::string kVideoRoot = PROJECT_ROOT + std::string("/resources/videos/");
const std::string kImageOutDir = PROJECT_ROOT + std::string("/out/video_screen/");
const std::string kVideoOutDir = PROJECT_ROOT + std::string("/out/");
const unsigned int kWindowWidth = 1280;
const unsigned int kWindowHeight = 720;
const char* kWindowName = "VideoScreen";
// 内容屏模板位置
auto gVideoScreen = std::make_shared<ScreenTemplate>(FusionMode::VIDEO);
// 键盘处理 解决帧率太快的问题
std::array<GLboolean, 1024> gKeys{};
// 记录“当前按下的键”和“已经被按下的键” 只处理未被处理过的按键
std::array<GLboolean, 1024> gKeysProcessed{};  // 处理过的按键

void processInput(GLFWwindow* window);
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void WriteRgbData(const std::string& filename, uint8_t* data, int width, int height);

void ReadVideoInfo(cv::VideoCapture& video_cap, const std::string& video_name) {
  // open video && load texture
  if (!video_cap.open(video_name)) {
    fprintf(stderr, "open video %s failed\n", video_name.c_str());
    exit(-1);
  }
  auto fps = video_cap.get(cv::CAP_PROP_FPS);
  auto frame_all = video_cap.get(cv::CAP_PROP_FRAME_COUNT);
  printf("video fps: %lf\n", fps);
  printf("video frame_all: %lf\n", frame_all);
  printf("video duration: %lfs\n", frame_all / fps);
  auto frame_h = video_cap.get(cv::CAP_PROP_FRAME_HEIGHT);
  auto frame_w = video_cap.get(cv::CAP_PROP_FRAME_WIDTH);
  printf("frame height: %lf, width: %lf\n", frame_h, frame_w);
}

void ConfigureShader(Shader* shader) {
  glm::mat4 projection = glm::ortho(0.0f, static_cast<float>(kWindowWidth),
                                    static_cast<float>(kWindowHeight), 0.0f, -1.0f, 1.0f);
  shader->UseProgram();
  shader->SetUniformMat4Float("projection", projection);
  shader->SetUniformInt("image", 0);
}

int main() {
  glfwInit();
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  glfwWindowHint(GLFW_RESIZABLE, GL_TRUE);

  GLFWwindow* window =
      glfwCreateWindow(kWindowWidth, kWindowHeight, kWindowName, nullptr, nullptr);
  glfwMakeContextCurrent(window);
  glfwSetKeyCallback(window, key_callback);
  glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

  // glad: load all OpenGL function pointers
  if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
    std::cout << "Failed to initialize GLAD" << std::endl;
    return -1;
  }
#ifdef _DEBUG
  printf("GL_VENDOR: %s\n", glGetString(GL_VENDOR));
  printf("GL_VERSION: %s\n", glGetString(GL_VERSION));
  printf("GL_EXTENSIONS: %s\n", glGetString(GL_EXTENSIONS));
  int ret;
  glGetIntegerv(GL_MAX_SAMPLES, &ret);  //
  printf("GL_MAX_SAMPLES: %d\n", ret);
  glGetIntegerv(GL_RENDERBUFFER_SAMPLES, &ret);  //
  printf("GL_RENDERBUFFER_SAMPLES: %d\n", ret);

  // 纹理采用由下面3个值决定
  glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &ret);
  printf("GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS: %d\n", ret);
  glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &ret);
  printf("GL_MAX_TEXTURE_IMAGE_UNITS: %d\n", ret);
  glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &ret);
  printf("GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: %d\n", ret);
  glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &ret);  // 支持的最大顶点属性数量
  printf("GL_MAX_VERTEX_ATTRIBS: %d\n", ret);
#endif
  // Load Shader
  ResourceManager::LoadShader("common", kShaderRoot + "shader.vert",
                              kShaderRoot + "shader.frag");
  ResourceManager::LoadShader("post", kShaderRoot + "post.vert",
                              kShaderRoot + "post.frag");
  // Configure Shader
  ConfigureShader(ResourceManager::GetShader("common").get());
  // Load Texture
  ResourceManager::LoadTextureWithoutData("meeting", 1920, 1080);
  ResourceManager::LoadTexture("background", kTextureRoot + "background_blue.jpg");
  cv::VideoCapture video_cap;
  ReadVideoInfo(video_cap,
                kVideoRoot + "WIN_20221108_15_59_00_Pro.mp4");  // meeting video
  // 大致在中间靠下
  gVideoScreen->InitVideoMode(4);
  auto meeting =
      std::make_shared<LenovoObject>(ResourceManager::GetTexture("meeting").get());
  // 定义四个人头框
  std::vector<std::shared_ptr<LenovoObject>> human_headers;
  {
    for (int i = 0; i < 4; ++i) {
      // 纹理 box_1, box_2, box_3, box4_
      ResourceManager::LoadTextureWithoutData("box_" + std::to_string(i), 1920, 1080);
      auto head = std::make_shared<LenovoObject>(
          ResourceManager::GetTexture("box_" + std::to_string(i)).get());
      human_headers.push_back(std::move(head));
      human_headers[i]->UpdateShader(ResourceManager::GetShader("common").get());
    }
  }
  double fps = 25.0;
  auto video_renderer = std::make_shared<VideoRenderer>();
  auto post_processor = std::make_shared<VideoPostProcessor>(
      ResourceManager::GetShader("post"), kWindowWidth, kWindowHeight);
#ifdef USE_VIDEO_WRITE
  cv::VideoWriter video_writer;
  // int codec = cv::VideoWriter::fourcc('X', '2', '6', '4');
  int codec = cv::VideoWriter::fourcc('a', 'v', 'c', '1');
  // int codec = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
  printf("codec = %d\n", codec);
  if (!video_writer.open(kVideoOutDir + "flash_with_post.mp4", codec, fps,
                         cv::Size(kWindowWidth, kWindowHeight), true)) {
    fprintf(stderr, "VideoWriter open %s failed\n", "flash_with_post.mp4");
    exit(-1);
  }
#endif
  int idx = 1;
  //  render loop
  while (!glfwWindowShouldClose(window)) {
    // input
    processInput(window);

    // render
    post_processor->BeginRender();
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    float time_value = static_cast<float>(glfwGetTimerValue());

    cv::Mat frame_mat;
    if (!video_cap.read(frame_mat)) {
      printf("Video finished, displaying beautiful blue screen!!!\n");
      std::this_thread::sleep_for(std::chrono::seconds(1));
      break;
    }
    cv::cvtColor(frame_mat, frame_mat, cv::COLOR_BGR2RGB);

    // Draw BackGround
    video_renderer->DrawStaticTexture(
        ResourceManager::GetShader("common").get(),
        ResourceManager::GetTexture("background").get(),  // texture
        fps,
        glm::vec2(0),                           // position
        glm::vec2(kWindowWidth, kWindowHeight)  // size
    );
    // Draw Box
    for (int i = 0; i < gVideoScreen->head_number; ++i) {
      human_headers[i]->DrawTransitionVideoScreenBoxCopy(
          video_renderer.get(), frame_mat, gVideoScreen->human_boxes[i], fps);
    }
    // Draw Meeting
    meeting->DrawVideoScreen(
        ResourceManager::GetShader("common").get(), video_renderer.get(), frame_mat.data,
        frame_mat.cols, frame_mat.rows, fps, gVideoScreen->position, gVideoScreen->size);
    post_processor->EndRender();
    post_processor->Render();
// SaveSingleFrame
#if 0
    cv::Mat out_mat{kWindowHeight, kWindowWidth, CV_8UC3};
    glPixelStorei(GL_PACK_ALIGNMENT, 4);
    glReadPixels(0, 0, kWindowWidth, kWindowHeight, GL_BGR, GL_UNSIGNED_BYTE,
                 out_mat.data);
    cv::flip(out_mat, out_mat, 0);  // filp mode: 0 x-axis 1 y-axis -1 both x and y axis
    char name[128] = {0};
    sprintf(name, "out-%04d.jpg", idx++);
    std::string filename = kImageOutDir + name;
    cv::imwrite(filename, out_mat);
    printf("%s\n", filename.c_str());
#ifdef USE_VIDEO_WRITE
    // video_writer.write(save_mat);
    video_writer << save_mat;
#endif
#endif
    glfwSwapBuffers(window);
    glfwPollEvents();
  }
  // optional: de-allocate all resources
  ResourceManager::Clear();
  video_renderer->Clear();
  post_processor->Clear();
  video_cap.release();
#ifdef USE_VIDEO_WRITE
  video_writer.release();
#endif
  // glfw: terminate, clearing all previously allocated GLFW resources.
  glfwTerminate();
  return 0;
}

void processInput(GLFWwindow* window) {
  if (gKeys[GLFW_KEY_DOWN] && !gKeysProcessed[GLFW_KEY_DOWN]) {
    gKeysProcessed[GLFW_KEY_DOWN] = GL_TRUE;
    // TODO:update box position and size
    gVideoScreen->UpdateVideoMode(gVideoScreen->head_number - 1);
  }
  if (gKeys[GLFW_KEY_UP] && !gKeysProcessed[GLFW_KEY_UP]) {
    // TODO:update box number
    gKeysProcessed[GLFW_KEY_UP] = GL_TRUE;
    gVideoScreen->UpdateVideoMode(gVideoScreen->head_number + 1);
  }
}

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode) {
  if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
    glfwSetWindowShouldClose(window, GL_TRUE);
  }
  if (key >= 0 && key < 1024) {
    if (action == GLFW_PRESS) {
      gKeys[key] = GL_TRUE;
    } else if (action == GLFW_RELEASE) {
      gKeys[key] = GL_FALSE;
      gKeysProcessed[key] = GL_FALSE;
    }
  }
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
  glViewport(0, 0, width, height);
}

void WriteRgbData(const std::string& filename, uint8_t* data, int width, int height) {
  const int data_size = width * height * 3;
  std::ofstream ofs(filename, std::ios::binary);
  if (!ofs.is_open()) {
    std::cout << "No Such File: " << filename << std::endl;
    return;
  }
  ofs.write(reinterpret_cast<const char*>(data), data_size);
  ofs.close();
}
